AutoScalingで管理されているEC2に自動的にCloudWatch Alarmを設定するLambda関数を作成してみた
AutoScalingで管理されているEC2に対してCloudWatch Alarmを設定するLambdaを作成したのでブログに残します。
やること
簡易的ですが構成図を作成しました。
AWSサービスとしてはAmazon EventBridge、Amazon SNS、Amazon SQS、AWS Lambdaを使用しています。
Amazon EventBridgeでAutoScalingのスケールイン、スケールアウト成功イベントを検知したらAmazon SNSへ連携を行います。
Amazon SNSからAmazon SQSへメッセージを送り、AWS Lambdaで処理を行ってアラームを設定します。
CloudWatch Agentを使用してメモリ使用率などを取得している場合、プロセスの起動が遅くてCloudWatchメトリクスの作成が遅れたりする可能性もあるのでAmazon SQSを使用して処理を開始するまでに遅延を持たせるような構成としました。
作成したコード
作成したコードは以下になります。
import json import boto3 import os cloudWatch = boto3.client('cloudwatch') sns_arn = os.environ['SNS_ARN'] def lambda_handler(event, context): body = json.loads(event['Records'][0]['body']) Message = json.loads(body['Message']) detail_type = Message['detail-type'] instance_id = Message['detail']['EC2InstanceId'] if detail_type == "EC2 Instance Launch Successful": cleate_alarms(instance_id) elif detail_type == "EC2 Instance Terminate Successful": cloudWatch.delete_alarms( AlarmNames=[ f'cpu_alarm_{instance_id}', f'mem_alarm_{instance_id}' ] ) def cleate_alarms(instance_id): # CPU使用率 cloudWatch.put_metric_alarm( AlarmName=f'cpu_alarm_{instance_id}', AlarmDescription='cpu usage over 50%', OKActions=[ sns_arn ], AlarmActions=[ sns_arn ], MetricName='CPUUtilization', Namespace='AWS/EC2', Statistic='Average', Dimensions=[ { 'Name':'InstanceId', 'Value': instance_id } ], Period=300, EvaluationPeriods=1, DatapointsToAlarm=1, Threshold=50, ComparisonOperator='GreaterThanOrEqualToThreshold' ) # メモリ使用率 cloudWatch.put_metric_alarm( AlarmName=f'mem_alarm_{instance_id}', AlarmDescription='memory usage over 50%', OKActions=[ sns_arn ], AlarmActions=[ sns_arn ], MetricName='mem_used_percent', Namespace='CWAgent', Statistic='Average', Dimensions=[ { 'Name':'InstanceId', 'Value': instance_id } ], Period=60, EvaluationPeriods=1, DatapointsToAlarm=1, Threshold=50, ComparisonOperator='GreaterThanOrEqualToThreshold' )
コードはPythonで作成しています。
AutoScalingのスケールイベントから「detail-type」と「EC2InstanceId」を取得してCloudWatch Alarmの作成と削除を行うようにしています。
今回はEC2にCloudWatch Agentをインストールしてメモリ使用率も取得したのでメモリ使用率のアラームも作成しています。
AWSリソースの作成
AutoScalingは以下のブログで作成したものを対象とさせていただきます。
Blue/Greenデプロイを行うとAutoScalingグループの接頭辞が「CodeDeploy_」になるのでEventBridgeのイベントパターンでワイルドカードを使用して指定するようにします。
また、EC2にはCloudWatch Agentをインストールしてメモリ使用率を取得するようにしています。
以下のドキュメントの手順でインストールしてからAMIを作成してからCodeDeployを作成してください。
エージェント設定を使用した EC2 インスタンスへの CloudWatch エージェントのインストール
Lambda関数作成
Lambda関数などの作成は以下のCloudFormationで行っています。
CloudFormationテンプレート (ここをクリックしてください)
AWSTemplateFormatVersion: "2010-09-09" Description: Lambda Stack Parameters: # ------------------------------------------------------------# # Parameters # ------------------------------------------------------------# Email: Type: String Resources: # ------------------------------------------------------------# # Lambda # ------------------------------------------------------------# LambdaIAMRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSLambdaSQSQueueExecutionRole Policies: - PolicyName: lambda-iam-policy PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - cloudwatch:PutMetricAlarm - cloudwatch:DeleteAlarms Resource: "*" Lambda: Type: AWS::Lambda::Function Properties: Code: ZipFile: | import json import boto3 import os cloudWatch = boto3.client('cloudwatch') sns_arn = os.environ['SNS_ARN'] def lambda_handler(event, context): body = json.loads(event['Records'][0]['body']) Message = json.loads(body['Message']) detail_type = Message['detail-type'] instance_id = Message['detail']['EC2InstanceId'] if detail_type == "EC2 Instance Launch Successful": cleate_alarms(instance_id) elif detail_type == "EC2 Instance Terminate Successful": cloudWatch.delete_alarms( AlarmNames=[ f'cpu_alarm_{instance_id}', f'mem_alarm_{instance_id}' ] ) def cleate_alarms(instance_id): # CPU使用率 cloudWatch.put_metric_alarm( AlarmName=f'cpu_alarm_{instance_id}', AlarmDescription='cpu usage over 50%', OKActions=[ sns_arn ], AlarmActions=[ sns_arn ], MetricName='CPUUtilization', Namespace='AWS/EC2', Statistic='Average', Dimensions=[ { 'Name':'InstanceId', 'Value': instance_id } ], Period=300, EvaluationPeriods=1, DatapointsToAlarm=1, Threshold=50, ComparisonOperator='GreaterThanOrEqualToThreshold' ) # メモリ使用率 cloudWatch.put_metric_alarm( AlarmName=f'mem_alarm_{instance_id}', AlarmDescription='memory usage over 50%', OKActions=[ sns_arn ], AlarmActions=[ sns_arn ], MetricName='mem_used_percent', Namespace='CWAgent', Statistic='Average', Dimensions=[ { 'Name':'InstanceId', 'Value': instance_id } ], Period=60, EvaluationPeriods=1, DatapointsToAlarm=1, Threshold=50, ComparisonOperator='GreaterThanOrEqualToThreshold' ) Environment: Variables: SNS_ARN: !Ref SNSEmailTopic FunctionName: create-alarm-lambda Handler: index.lambda_handler Role: !GetAtt LambdaIAMRole.Arn Runtime: python3.12 Timeout: 5 EventSource: Type: AWS::Lambda::EventSourceMapping Properties: EventSourceArn: !GetAtt SQSQueue.Arn FunctionName: create-alarm-lambda # ------------------------------------------------------------# # SNS # ------------------------------------------------------------# SNSEmailTopic: Type: AWS::SNS::Topic Properties: FifoTopic: false TopicName: email-sns-topic EmailSubscription: Type: AWS::SNS::Subscription Properties: Endpoint: !Ref Email Protocol: email TopicArn: !Ref SNSEmailTopic SNSSQSTopic: Type: AWS::SNS::Topic Properties: FifoTopic: false TopicName: sqs-sns-topic SQSSubscription: DependsOn: SQSQueue Type: AWS::SNS::Subscription Properties: Endpoint: !GetAtt SQSQueue.Arn Protocol: sqs TopicArn: !Ref SNSSQSTopic SNSSQSTopicPolicy: Type: AWS::SNS::TopicPolicy Properties: PolicyDocument: Statement: - Effect: Allow Principal: Service: events.amazonaws.com Action: sns:Publish Resource: "*" Topics: - !Ref SNSSQSTopic # ------------------------------------------------------------# # SQS # ------------------------------------------------------------# SQSQueue: Type: AWS::SQS::Queue Properties: DelaySeconds: 10 QueueName: sqs-queue SqsManagedSseEnabled: true Tags: - Key: Name Value: sqs-queue VisibilityTimeout: 30 SQSQueuePolicy: DependsOn: SNSSQSTopic Type: AWS::SQS::QueuePolicy Properties: PolicyDocument: Version: "2012-10-17" Statement: - Sid: "Account used" Effect: "Allow" Principal: AWS: "*" Action: - "SQS:*" Resource: !GetAtt SQSQueue.Arn - Sid: "SNS used" Effect: "Allow" Principal: Service: "sns.amazonaws.com" Action: - "SQS:SendMessage" Resource: !GetAtt SQSQueue.Arn Condition: ArnLike: aws:SourceArn: !Ref SNSSQSTopic Queues: - !Ref SQSQueue # ------------------------------------------------------------# # EventBridge # ------------------------------------------------------------# EventBridge: Type: AWS::Events::Rule Properties: EventPattern: source: - aws.autoscaling detail-type: - EC2 Instance Launch Successful - EC2 Instance Terminate Successful detail: AutoScalingGroupName: - wildcard: CodeDeploy_* Name: autoscaling-eventbridge State: ENABLED Targets: - Arn: !Ref SNSSQSTopic Id: sns-topic
16~134行目でLambda関数に関連するリソースを作成しています。
Lambdaで使用するIAMロールにはAWSマネージドポリシーのAWSLambdaSQSQueueExecutionRoleとCloudWatch Alarmの作成、削除を行えるように「cloudwatch:PutMetricAlarm」、「cloudwatch:DeleteAlarms」を許可するインラインポリシーを設定しています。
139~177行目でSNSトピックを作成しています。
SNSトピックはCloudWatch Alarmで使用するものとSQSへメッセージを送るためのものを作成しています。
182~218行目でSQSキューを作成しています。
SQSキューではDelaySecondsでメッセージを処理できるようにするまで10秒間遅延を持たせています。
また、キューポリシーでSNSトピックからの「SQS:SendMessage」を許可しています。
222~238行目でEventBridgeを作成しています。
EventBridgeのルールは以下のようにすることで「CodeDeploy_」から始まるAutoScalingグループを対象とすることができます。
{ "detail-type": ["EC2 Instance Launch Successful", "EC2 Instance Terminate Successful"], "source": ["aws.autoscaling"], "detail": { "AutoScalingGroupName": [{ "wildcard": "CodeDeploy_*" }] } }
デプロイは以下のAWS CLIコマンドを使用します。
aws cloudformation create-stack --stack-name CloudFormationスタック名 --template-body file://CloudFormationテンプレートファイル名 --parameters ParameterKey=Email,ParameterValue=メールアドレス --capabilities CAPABILITY_NAMED_IAM
動作確認
リソースの作成が完了したら実際にCodePipelineを動かしてデプロイを開始してみてください。
Lambda関数の実行が成功すると以下の画像のようにCloudWatch Alarmが作成されることが確認できます。
さいごに
AutoScalingで管理されたEC2に対してCloudWatch Alarmを設定するとアプリ起因による継続的なリソースの負荷増加など、インスタンスの入れ替えでは解決できない状況を検知するのに役立つことがあります。
状況に応じて上記のようなLambda関数を導入していただければと思います。